import type {
  ZodArray,
  ZodDate,
  ZodOptional,
  ZodString,
  ZodTypeAny,
  z,
} from "zod";

type OptionableZodType<T extends ZodTypeAny> = T | ZodOptional<T>;

export interface ZodLayoutMetadata<
  T extends ZodTypeAny,
  TInferred = z.input<T> | z.output<T>,
> {
  accordion?: string;
  label?: string;
  inputType?: "variableDropdown" | "textarea" | "password" | "code";
  lang?: "javascript" | "html" | "css" | "json";
  defaultValue?: T extends ZodDate ? string : TInferred;
  placeholder?: string;
  helperText?: string;
  direction?: "row" | "column";
  isRequired?: boolean;
  withVariableButton?: boolean;
  fetcher?: T extends OptionableZodType<ZodString> ? string : never;
  itemLabel?: T extends OptionableZodType<ZodArray<any>> ? string : never;
  isOrdered?: T extends OptionableZodType<ZodArray<any>> ? boolean : never;
  moreInfoTooltip?: string;
  isHidden?: boolean | ((currentObj: Record<string, any>) => boolean);
  isDebounceDisabled?: boolean;
  hiddenItems?: string[] | readonly string[];
  mergeWithLastField?: boolean;
  /** Useful for string options with fetcher when we also want to allow for custom text */
  toLabels?: (val?: string) => string | undefined;
  autoCompleteItems?: string[];
}

declare module "zod" {
  interface ZodType {
    layout<T extends ZodTypeAny>(this: T, metadata: ZodLayoutMetadata<T>): T;
  }

  interface ZodTypeDef {
    layout?: ZodLayoutMetadata<ZodTypeAny>;
  }
}

export const extendWithTypebotLayout = (zod: typeof z) => {
  if (typeof zod.ZodType.prototype.layout !== "undefined") {
    return;
  }

  zod.ZodType.prototype.layout = function (layout) {
    const result = new (this as any).constructor({
      ...this._def,
      layout,
    });

    return result;
  };
};
